/*
 * Copyright 2024 Redpanda Data, Inc.
 *
 * Use of this software is governed by the Business Source License
 * included in the file licenses/BSL.md
 *
 * As of the Change Date specified in that file, in accordance with
 * the Business Source License, use of this software will be governed
 * by the Apache License, Version 2.0
 */

#include "container/chunked_vector.h"
#include "container/tests/bench_utils.h"
#include "random/generators.h"

#include <seastar/testing/perf_tests.hh>

#include <algorithm>
#include <concepts>
#include <iterator>
#include <vector>

template<typename Vector, size_t Size>
struct VectorBenchTest {
    using value_type = typename Vector::value_type;

    const value_type val = make_value();
    const Vector filled = make_filled();

    // copy overloads copy a T either via ctor (this one)
    auto copy(const std::copy_constructible auto& t) { return t; }

    // or a .copy method (this one)
    template<typename T>
    requires requires(const T& t) { t.copy(); }
    auto copy(const T& t) {
        return t.copy();
    }

    static auto make_value() {
        return ::make_value<typename Vector::value_type>();
    }

    static Vector make_filled() {
        Vector v;
        std::generate_n(std::back_inserter(v), Size, make_value);
        return v;
    }

    std::vector<size_t> indexes = [] {
        random_generators::rng rng{0};
        std::vector<size_t> indexes;
        std::generate_n(std::back_inserter(indexes), 1000, [&]() {
            return rng.get_int(0uz, Size - 1);
        });
        return indexes;
    }();

    [[gnu::noinline]]
    void run_sort_test() {
        auto v = copy(filled);
        std::sort(v.begin(), v.end());
    }

    [[gnu::noinline]]
    void run_fifo_test() {
        Vector v;
        for (size_t i = 0; i < Size; ++i) {
            // NOLINTNEXTLINE(performance-inefficient-vector-operation)
            v.push_back(val);
        }
        perf_tests::do_not_optimize(v);
    }

    [[gnu::noinline]]
    void run_lifo_test() {
        Vector v;
        for (size_t i = 0; i < Size; ++i) {
            v.push_back(val);
        }
        while (!v.empty()) {
            v.pop_back();
        }
    }

    [[gnu::noinline]]
    void run_fill_test() {
        Vector v = make_filled();
        perf_tests::do_not_optimize(v);
    }

    [[gnu::noinline]]
    void run_random_access_test() {
        for (size_t index : indexes) {
            perf_tests::do_not_optimize(filled[index]);
        }
    }
};

// NOLINTBEGIN(*-macro-*)
#define VECTOR_PERF_TEST(container, element, size)                             \
    class VectorBenchTest_##container##_##element##_##size                     \
      : public VectorBenchTest<container<element>, size> {};                   \
    PERF_TEST_F(VectorBenchTest_##container##_##element##_##size, Sort) {      \
        run_sort_test();                                                       \
    }                                                                          \
    PERF_TEST_F(VectorBenchTest_##container##_##element##_##size, Fifo) {      \
        run_fifo_test();                                                       \
    }                                                                          \
    PERF_TEST_F(VectorBenchTest_##container##_##element##_##size, Lifo) {      \
        run_lifo_test();                                                       \
    }                                                                          \
    PERF_TEST_F(VectorBenchTest_##container##_##element##_##size, Fill) {      \
        run_fill_test();                                                       \
    }                                                                          \
    PERF_TEST_F(                                                               \
      VectorBenchTest_##container##_##element##_##size, RandomAccess) {        \
        run_random_access_test();                                              \
    }
// NOLINTEND(*-macro-*)

template<typename T>
using std_vector = std::vector<T>;
using ss::sstring;

VECTOR_PERF_TEST(std_vector, int64_t, 64)
VECTOR_PERF_TEST(chunked_vector, int64_t, 64)

VECTOR_PERF_TEST(std_vector, sstring, 64)
VECTOR_PERF_TEST(chunked_vector, sstring, 64)

VECTOR_PERF_TEST(std_vector, large_struct, 64)
VECTOR_PERF_TEST(chunked_vector, large_struct, 64)

VECTOR_PERF_TEST(std_vector, int64_t, 10000)
VECTOR_PERF_TEST(chunked_vector, int64_t, 10000)

VECTOR_PERF_TEST(std_vector, sstring, 10000)
VECTOR_PERF_TEST(chunked_vector, sstring, 10000)

VECTOR_PERF_TEST(std_vector, large_struct, 10000)
VECTOR_PERF_TEST(chunked_vector, large_struct, 10000)

VECTOR_PERF_TEST(std_vector, int64_t, 1048576)
VECTOR_PERF_TEST(chunked_vector, int64_t, 1048576)
